Skip to content

feat: RAG follow-ups — kb/query metadata filtering, recursive chunking, Cohere Bedrock embeddings (#153)#171

Merged
michaelmcnees merged 3 commits into
mainfrom
claude/office-assistant-mantle-feasibility-n8f4bk
Jul 4, 2026
Merged

feat: RAG follow-ups — kb/query metadata filtering, recursive chunking, Cohere Bedrock embeddings (#153)#171
michaelmcnees merged 3 commits into
mainfrom
claude/office-assistant-mantle-feasibility-n8f4bk

Conversation

@michaelmcnees

@michaelmcnees michaelmcnees commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Three composable follow-ups on the RAG primitives from #153.

Changes

  • kb/query metadata filtering — an optional filter object scopes the vector search to rows whose metadata contains it, via the JSONB containment operator (metadata @> $2::jsonb). The whole filter binds as a single parameter (no injection surface); a custom metadata_column is supported, and an empty object is ignored. Pure prepareQuery core.
  • Separator-aware recursive chunkingtext/chunk gains unit: recursive, which walks a separator hierarchy (paragraph → line → sentence → word → character) so chunks break on natural boundaries instead of mid-sentence, then merges pieces up to chunk_size with chunk_overlap characters carried between chunks. Unicode-aware. rag-ingest now uses it.
  • Cohere Bedrock embeddingsai/embed (provider bedrock) adds cohere.embed-english-v3 and cohere.embed-multilingual-v3 alongside Amazon Titan. Cohere batches natively (up to 96 texts per call) and takes an input_type (search_document default / search_query / classification / clustering), threaded through EmbeddingRequest. Cohere's Bedrock response carries no token counts, so those embeddings report zero usage.

Docs (RAG guide, connector reference), both RAG examples, and a changeset are updated.

Testing

  • Unit tests for all three: prepareQuery filter (placeholder positioning, custom column, empty-filter omission, non-object/injection rejection); recursive chunking (sentence boundaries, hard-split fallback, overlap, size bound); Cohere embeddings (batching, default/invalid input_type, order preservation).
  • go build ./cmd/mantle, go vet ./..., gofmt (changed files) clean.
  • mantle validate passes on rag-ingest.yaml and rag-ask.yaml.
  • Integration tests (testcontainers) run in CI only.

Related Issues

Follow-ups on #153. Token-accurate (tokenizer-based) chunking remains the one open item there.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added optional metadata filtering for knowledge base queries.
    • Added a new recursive text chunking mode that follows natural boundaries.
    • Expanded embedding support for additional Bedrock models, including new input-type options.
  • Bug Fixes

    • Improved chunk sizing behavior to better respect overlap and size limits.
    • Clarified and tightened handling of invalid query filters and metadata fields.
  • Documentation

    • Updated RAG guides and examples to show the new query, chunking, and embedding options.

…g, Cohere Bedrock embeddings (#153)

Three composable follow-ups on the RAG primitives (#153):

- kb/query metadata filtering: an optional `filter` object restricts the
  search to rows whose metadata contains it via JSONB containment
  (metadata @> $2::jsonb). The filter binds as a single parameter (no
  injection surface); a custom metadata_column is supported. Pure
  prepareQuery core with unit tests (placeholder positioning, custom
  column, empty-filter omission, non-object/injection errors).

- text/chunk recursive mode: `unit: recursive` walks a separator
  hierarchy (paragraph -> line -> sentence -> word -> character) so
  chunks break on natural boundaries, then merges pieces up to
  chunk_size with chunk_overlap characters carried between chunks.
  Pure, Unicode-aware, unit-tested (sentence boundaries, hard-split
  fallback, overlap, size bound). rag-ingest now uses it.

- Cohere Bedrock embeddings: ai/embed (provider bedrock) adds
  cohere.embed-english-v3 / cohere.embed-multilingual-v3 alongside
  Titan. Cohere batches natively (<=96 texts/call) and takes an
  input_type (search_document default / search_query / classification /
  clustering), threaded through EmbeddingRequest. Tests cover batching,
  default/invalid input_type, and order preservation.

Docs (RAG guide, connector reference) and examples updated; changeset added.
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@michaelmcnees, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f3cfbdd-5df5-4cc3-b145-78e2f2cafff4

📥 Commits

Reviewing files that changed from the base of the PR and between 70af90e and 5bf172b.

📒 Files selected for processing (5)
  • packages/engine/internal/connector/chunk.go
  • packages/engine/internal/connector/kb.go
  • packages/engine/internal/connector/provider_bedrock.go
  • packages/engine/internal/connector/provider_bedrock_embed_test.go
  • packages/site/src/content/docs/workflow-reference/connectors.md
📝 Walkthrough

Walkthrough

This PR adds three related RAG capabilities to the engine: optional JSONB metadata filtering for kb/query, a separator-aware recursive chunking mode for text/chunk, and Cohere Bedrock embedding support (with batching and input_type) for ai/embed. Documentation and examples are updated accordingly.

Changes

RAG follow-ups

Layer / File(s) Summary
kb/query metadata filter
packages/engine/internal/connector/kb.go, packages/engine/internal/connector/kb_test.go
prepareQuery gains a kbFilterClause helper producing an optional WHERE metadata_column @> $N::jsonb fragment, validated by new tests covering custom columns, empty filters, and invalid inputs.
Recursive text chunking
packages/engine/internal/connector/chunk.go, packages/engine/internal/connector/chunk_test.go
chunkText supports a new "recursive" unit using a separator hierarchy, splitRecursive, hardSplit, and mergeSplits with rune-based size/overlap, covered by new tests for sentence splitting, fallback, overlap, and size bounds.
Cohere Bedrock embeddings and input_type
packages/engine/internal/connector/provider.go, embed.go, provider_bedrock.go, provider_bedrock_embed_test.go
EmbeddingRequest.InputType is added and wired through Execute; BedrockEmbeddingProvider.Embeddings dispatches to Titan or new cohereEmbeddings (batched up to 96 inputs, input_type defaulting/validation), validated by new tests.
Documentation and examples
.changeset/rag-followups.md, packages/site/src/content/docs/rag-guide.md, packages/site/src/content/docs/workflow-reference/connectors.md, packages/site/src/content/examples/rag-ask.yaml, packages/site/src/content/examples/rag-ingest.yaml
Documents metadata filtering, recursive chunking, and Cohere Bedrock embedding parameters/models in the changeset, RAG guide, connector reference, and example workflows.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

  • dvflw/mantle#168: Both PRs modify the Bedrock embedding provider (embed.go, provider_bedrock.go), extending the same Titan baseline with new capabilities.
  • dvflw/mantle#169: Both PRs modify prepareQuery in kb.go, with this PR extending it to support JSONB metadata filtering.
  • dvflw/mantle#170: This PR extends the chunkText/chunk_test.go connector introduced in that PR with a new recursive chunking mode.

Poem

A rabbit hops through JSONB fields,
Filters bloom where metadata yields.
Chunks now split at sentence's end,
Cohere's whispers batch and blend. 🐇
Hop, hop, ship it — docs in tow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the three main RAG follow-ups and references the related issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/office-assistant-mantle-feasibility-n8f4bk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70af90efaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

switch {
case strings.HasPrefix(req.Model, "amazon.titan-embed"):
return p.titanEmbeddings(ctx, req)
case strings.HasPrefix(req.Model, "cohere.embed"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unsupported Cohere model IDs before dispatch

When model is a valid but unsupported Cohere Bedrock ID such as cohere.embed-v4:0, this prefix match routes it into the v3 request path instead of returning the unsupported-model error advertised below. AWS documents Cohere v4 as a separate model with different parameters, including truncate: LEFT|RIGHT|NONE, so the hard-coded v3 body with truncate: "END" can fail at runtime or return embeddings with dimensions this connector does not handle; restrict this branch to the two v3 IDs the connector actually supports.

Useful? React with 👍 / 👎.

The broad HasPrefix(model, "cohere.embed") match routed any Cohere ID —
including newer models like cohere.embed-v4:0, which use a different
request/response shape (truncate LEFT|RIGHT|NONE, different output) — into
the v3 code path with a hard-coded v3 body. Match only the two v3 families
(cohere.embed-english-v3 / -multilingual-v3) so unsupported IDs fall through
to the unsupported-model error. Adds a regression test for v4.
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/engine/internal/connector/kb.go (1)

299-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Misleading nextParam semantics in kbFilterClause.

The doc comment says nextParam is "the 1-based index of the next free placeholder ($1 is always the query vector)", but the caller passes len(args) (which is 1 for the vector-only case) and the function itself adds +1 to compute the actual placeholder ($2). So nextParam is really "the count of already-bound params," not "the next free index" as documented. The result is correct, but the naming/comment could confuse future maintainers into passing an off-by-one value.

✏️ Suggested clarification
-// An absent or empty filter yields no clause. nextParam is the 1-based index of
-// the next free placeholder ($1 is always the query vector).
-func kbFilterClause(params map[string]any, nextParam int) (kbFilter, error) {
+// An absent or empty filter yields no clause. boundParamCount is the number of
+// SQL parameters already bound (currently always 1, for the query vector);
+// the filter placeholder is boundParamCount+1.
+func kbFilterClause(params map[string]any, boundParamCount int) (kbFilter, error) {

And update the usage of nextParam+1 to boundParamCount+1 accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/internal/connector/kb.go` around lines 299 - 327, The
`kbFilterClause` comment and parameter name are misleading because the caller
passes the count of already-bound args, not the “next free placeholder” index;
clarify this by renaming `nextParam` to `boundParamCount` (or equivalent) in
`kbFilterClause` and updating the placeholder expression to use that meaning
consistently. Also update the doc comment to say it’s the number of
already-bound parameters, since `$1` is reserved for the query vector and the
filter should bind at `boundParamCount + 1`. Keep the behavior unchanged, but
make the naming and comment match the actual usage in the `kbFilterClause`
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/engine/internal/connector/chunk.go`:
- Around line 121-151: The mergeSplits chunking contract is inconsistent with
the current behavior in mergeSplits, since flushing then retaining overlap can
produce chunks larger than size; update the implementation to either enforce the
max before appending or adjust the documented/tests contract to reflect the real
size + overlap bound. Check the mergeSplits function and any related chunk-size
docstrings or expectations in connector/chunk.go so the code and comments agree.

In `@packages/site/src/content/docs/workflow-reference/connectors.md`:
- Around line 407-412: The Markdownlint MD038 failure comes from an inline code
span in the chunking strategy description using leading or trailing whitespace.
Inspect the affected markdown in the connectors documentation around the `unit`
field and the `recursive` chunking description, then trim any spaces inside
backticks so every inline code span is flush with its content.

---

Nitpick comments:
In `@packages/engine/internal/connector/kb.go`:
- Around line 299-327: The `kbFilterClause` comment and parameter name are
misleading because the caller passes the count of already-bound args, not the
“next free placeholder” index; clarify this by renaming `nextParam` to
`boundParamCount` (or equivalent) in `kbFilterClause` and updating the
placeholder expression to use that meaning consistently. Also update the doc
comment to say it’s the number of already-bound parameters, since `$1` is
reserved for the query vector and the filter should bind at `boundParamCount +
1`. Keep the behavior unchanged, but make the naming and comment match the
actual usage in the `kbFilterClause` helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 958abe18-dff7-4e9c-a606-960a45c65d59

📥 Commits

Reviewing files that changed from the base of the PR and between 0f4a3f6 and 70af90e.

📒 Files selected for processing (13)
  • .changeset/rag-followups.md
  • packages/engine/internal/connector/chunk.go
  • packages/engine/internal/connector/chunk_test.go
  • packages/engine/internal/connector/embed.go
  • packages/engine/internal/connector/kb.go
  • packages/engine/internal/connector/kb_test.go
  • packages/engine/internal/connector/provider.go
  • packages/engine/internal/connector/provider_bedrock.go
  • packages/engine/internal/connector/provider_bedrock_embed_test.go
  • packages/site/src/content/docs/rag-guide.md
  • packages/site/src/content/docs/workflow-reference/connectors.md
  • packages/site/src/content/examples/rag-ask.yaml
  • packages/site/src/content/examples/rag-ingest.yaml

Comment thread packages/engine/internal/connector/chunk.go Outdated
Comment thread packages/site/src/content/docs/workflow-reference/connectors.md Outdated
…Clause naming

- chunk.go: mergeSplits/chunkText docs now state a recursive chunk targets
  `size` and may reach `size + overlap` (carried overlap tail), matching the
  size-bound test and the connector reference — not "at most size".
- connectors.md: quote the recursive separators (`". "`, `" "`, etc.) so no
  inline code span has a leading/trailing space (markdownlint MD038).
- kb.go: rename kbFilterClause's `nextParam` to `boundParamCount` and clarify
  the doc — it's the count of already-bound params ($1 = vector), so the filter
  binds at boundParamCount+1. Behavior unchanged.
@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@michaelmcnees michaelmcnees merged commit 1f017e4 into main Jul 4, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants